/**
 *    Copyright 2009-2015 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.parsing;

/**
 * @author Clinton Begin
 */
/**
 * 普通记号解析器，处理#{}和${}参数
 * 
 */
public class GenericTokenParser {

	/**
	 * 开始记号
	 */
	private final String openToken;
	/**
	 * 结束标识
	 */
	private final String closeToken;
	/**
	 * 记号处理器
	 */
	private final TokenHandler handler;

	/**
	 * 利用带参数的构造函数初始化各项属性
	 * 
	 * @param openToken 开始记号
	 * @param closeToken 结束标识
	 * @param handler 记号处理器
	 */
	public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
		this.openToken = openToken;
		this.closeToken = closeToken;
		this.handler = handler;
	}

	/**
	 * 通过参数的开始标记与结束标记，循环获取SQL串中的参数，并对其进行一定的处理，组合成新串之后，架构新串返回
	 * 
	 * @param text
	 *            
	 * @return String
	 */
	public String parse(String text) {
		StringBuilder builder = new StringBuilder();
		//如果传入的字符串有值
		if (text != null && text.length() > 0) {
			//将字符串转为字符数组
			char[] src = text.toCharArray();
			int offset = 0;
			//判断openToken在text中的位置，注意indexOf函数的返回值-1表示不存在，0表示在在开头的位置
			int start = text.indexOf(openToken, offset);
			// #{favouriteSection,jdbcType=VARCHAR}
			// 这里是循环解析参数，参考GenericTokenParserTest,比如可以解析${first_name} ${initial} ${last_name} reporting.这样的字符串,里面有3个 ${}
			// 验证start是否大于-1（亦即给定参数text中存在openToken子串），如果大于-1（开启循环），验证在给定text的start位置的前一位字符是否为"\"（反斜扛），如果是反斜杠，说明获取到的参数被屏蔽了，我们需要去除这个反斜杠，并重新定位offset。当然如果不是反斜扛，说明参数正常
			while (start > -1) {
				// 判断一下 ${ 前面是否是反斜杠，这个逻辑在老版的mybatis中（如3.1.0）是没有的
				if (start > 0 && src[start - 1] == '\\') {
					// the variable is escaped. remove the backslash.
					// 新版已经没有调用substring了，改为调用如下的offset方式，提高了效率
					 //如果text中在openToken前存在转义符就将转义符去掉。如果openToken前存在转义符，start的值必然大于0，最小也为1
			          //因为此时openToken是不需要进行处理的，所以也不需要处理endToken。接着查找下一个openToken
					builder.append(src, offset, start - offset - 1).append(openToken);
					offset = start + openToken.length(); //重设offset
				} else {
					// 获取第一个匹配子串的末位位置end，如果end为-1，表示不存在closeToken，则获取末位end之前的所有串，并重新定位offset为src数组长度，如果end值不是-1，说明text字符串中存在结束标记closeToken
					int end = text.indexOf(closeToken, start);
					//如果不存在openToken，则直接将offset位置后的字符添加到builder中
					if (end == -1) {
						builder.append(src, offset, src.length - offset);
						offset = src.length; //重设offset
					} else {
						// 获取开始标记之前的子串，并重新定位偏移量offset（start+开始标记的长度=具体参数开始位置），获取这个参数串为content，然后调用TokenHandler的handleToken()方法对获取到的参数串进行处理（比如替换参数之类），然后将处理后的串添加到之前的子串之上，再次重新定位偏移量offset为结束标记的下一位（end+closeToken的长度=end+1）
						builder.append(src, offset, start - offset);
						offset = start + openToken.length(); //重设offset
						String content = new String(src, offset, end - offset);
						// 得到一对大括号里的字符串后，调用handler.handleToken,比如替换变量这种功能
						builder.append(handler.handleToken(content));
						offset = end + closeToken.length(); //重设offset
					}
				}
				// 获取text中下一步openToken的开始位置，重置start，执行循环体第三步到第六步，处理每一个参数，直到最后一个参数，循环结束
				start = text.indexOf(openToken, offset);
			}
			// 验证偏移量offset与src数组的长度，如果offset小，说明原串还有部分未添加到新串之上，将末尾剩余部分添加到新串，然后将新串返回，如果offset不小于src的数组长度，则直接返回新串
			if (offset < src.length) {
				builder.append(src, offset, src.length - offset);
			}
		}
		return builder.toString();
	}

}
